/*
 *  db.cpp
 *  
 *
 *  Created by Shuqiao Guo on 12/20/11.
 *  Copyright 2011 Thothe. All rights reserved.
 *
 */

#include <cstring>
#include <iostream>

#include "access/db.h"

using namespace std;

#define UINT_32_SIZE 4
#define UINT_64_SIZE 8

Database::Database(const char* name, uint8_t column_count, KeyType type, BufferManager* man)
{
	strcpy(index_name, name);
	
	dim = column_count;
	
	unit_size = 0;
	
	index_type = new AttributeType[dim];
	
	for(int i = 0; i < dim; i++)
	{
		index_type[i] = type[i];
		
		if(type[i] == kShort)
			unit_size += UINT_32_SIZE;
		else if(type[i] == kInt)
			unit_size += UINT_64_SIZE;
		else if(type[i] == kVarchar)
			unit_size += MAX_VARCHAR_LENGTH;
	}

    unit_size += MAX_PAYLOAD_LENGTH + 1;
	
    bm = man;
	
	headPage == NULL;

    db_mutex = PTHREAD_MUTEX_INITIALIZER;
}

Database::Database(const Database &d)
{
    strcpy(index_name, d.index_name);
    dim = d.dim;
    unit_size = d.unit_size;
    
    index_type = new AttributeType[dim];

	for(int i = 0; i < dim; i++)
	{
		index_type[i] = d.index_type[i];
	}
    
    bm = d.bm;
    
    headPage = d.headPage;
    
    db_mutex = PTHREAD_MUTEX_INITIALIZER;
}

Database::~Database()
{
	delete [] index_type;
}

void Database::ReportInfo()
{
    cout << endl;
	cout << "Database Name : " << index_name << endl;
	cout << "Dimension     : " << ((int)dim) << endl;
	cout << "Index Type    : ";
	
	for(int i = 0; i < dim; i++)
	{
			if(index_type[i] == kShort)
				cout << "kShort ";
			else if(index_type[i] == kInt)
				cout << "kInt ";
			else if(index_type[i] == kVarchar)
				cout << "kVarchar ";
	}

	cout << endl;
	
	cout << "Unit Size     : " << unit_size << endl;

	cout << endl;

    DatumPage* curPage = headPage;
    
    while(curPage)
    {
        //cout << "DB_PAGE : " << curPage << endl;
        
        curPage->PrintAllRecords();
        
        //cout << curPage->GetNextDatumPage() << endl;
        curPage = curPage->GetNextDatumPage();
    }
}

bool Database::InsertRecord(Record* rec, Transaction* trans)
{
    void* copyLink = trans->FindLink((void*)this);
    
    Database* copyDB = (Database*)copyLink;

    pthread_mutex_t* cur_mutex;
 

    if(!copyDB)
    {
        cur_mutex = LockPage();
        
        copyDB = new Database(*this);
        
        trans->AddDatabase(cur_mutex, this, copyDB);
    }
    
	AccessType t = WriteMode;
	
	if(copyDB->headPage == NULL)
    {
        copyDB->headPage = new DatumPage(unit_size, bm, t, index_type, dim);
        
        pthread_mutex_t *page_mutex = copyDB->headPage->LockPage();
        
        trans->AddDatumPage(page_mutex, NULL, copyDB->headPage);
    }
	
    DatumPage* curPage = copyDB->headPage;
    
    
    while(curPage)
    {
        DatumPage* newDatumPage;
        
        
        copyLink = trans->FindLink(curPage);
        
        newDatumPage = (DatumPage*)copyLink;
        
        bool isNewCopy = false;
        
        if(!newDatumPage)
        {
            isNewCopy = true;
            
            pthread_mutex_t *cur_page_mutex = curPage->LockPage();
        
            newDatumPage = new DatumPage(*curPage);

            trans->AddDatumPage(cur_page_mutex, curPage, newDatumPage);
        }
        
        //newDatumPage->PrintAllRecords();

        if(newDatumPage->InsertRecord(rec))
        {
            //newDatumPage->PrintAllRecords();
            break;
        }
        else if(isNewCopy)
        {
            trans->AbortLast(this);
        }
        curPage = curPage->GetNextDatumPage();
    }
    
    // there's no free slot on any of the pages under the database
    // we have to create a new datum page
    if(!curPage)
    {
        DatumPage* newPage = new DatumPage(unit_size, bm, t, index_type, dim);
        
        pthread_mutex_t *cur_page_mutex = newPage->LockPage();
        
        trans->AddDatumPage(cur_page_mutex, NULL, newPage);
        
        newPage->SetNextPage(copyDB->headPage);

        copyDB->headPage = newPage;
        
        newPage->InsertRecord(rec);
        
        //newPage->PrintAllRecords();
    }
    
	return true;
}

void Database::CopyDatabase(Database* dest, Database* source)
{
    if(!source)
    {
        delete dest;
        
        return;
    }
    
    // all other information is supposed to be exactly the same
    // we only need to update the link to the datum pages
    dest->headPage = source->headPage;
}

pthread_mutex_t* Database::LockPage()
{
    pthread_mutex_lock(&db_mutex);
    
    return &db_mutex;
}
